﻿using SharpDX.Direct3D11;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Windows;
using Device = SharpDX.Direct3D11.Device;
using Resource = SharpDX.Direct3D11.Resource;
using System.Collections.Generic;
using System.IO;
using System;
using ProtoBuf;
using System.Xml.Serialization;

namespace Engine
{
    /// <summary>
    /// Управление ресурсами
    /// </summary>
    public static class ContentManager
    {
        private static Dictionary<string, WeakReference> Textures = new Dictionary<string, WeakReference> { };
        private static Dictionary<string, WeakReference> Meshes = new Dictionary<string, WeakReference> { };
        private static Dictionary<string, WeakReference> MultiMesh = new Dictionary<string, WeakReference> { };
        private static Dictionary<string, WeakReference> Effects = new Dictionary<string, WeakReference> { };
        
        /// <summary>
        /// Получить отдельный мэш
        /// </summary>
        /// <param name="FileName"></param>
        /// <returns></returns>
        public static MeshContainer LoadMesh(string FileName)
        {
            string Origin = FileName;
            if (Meshes.ContainsKey(Origin) && Meshes[Origin].IsAlive)
            {
                //Уже есть
                return (MeshContainer)Meshes[Origin].Target;
            }
            else
            {
                //нужно загрузить
                MeshContainer _MConteints = null;

                if (File.Exists(FileName + ".mesh"))
                {
                    FileName += ".mesh";
                }

                string extension = System.IO.Path.GetExtension(FileName);

                if (extension.ToLower() == ".mesh")
                {
                    try
                    {
                        System.IO.FileStream fs = new System.IO.FileStream(FileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                        Serialize.Mesh M = Serializer.Deserialize<Serialize.Mesh>(fs);
                        fs.Close();
                        if (M != null) _MConteints = new MeshContainer(M);
                        else return null;
                    }
                    catch (System.Exception ex)
                    {
                        System.Windows.Forms.MessageBox.Show("Error! Ошибкак чтения модели: " + FileName);
                        new FileNotFoundException("Файл", FileName);
                    }
                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("Error! Неверный тип файла: " + FileName);
                    new FileNotFoundException("Неверный тип файла", FileName);
                }

                WeakReference wr = new WeakReference(_MConteints, false);
                Meshes.Add(Origin, wr);
                _MConteints.Name = Origin;
                return _MConteints;
                //TODO проверка на валидность загрузки
            }
        }

        /// <summary>
        /// Получить мультимеш мэш
        /// </summary>
        /// <param name="FileName"></param>
        /// <returns></returns>
        public static MultiMeshContainer LoadMultiMesh(string FileName)
        {
            string Origin = FileName;
            if (MultiMesh.ContainsKey(Origin) && MultiMesh[Origin].IsAlive)
            {
                //Уже есть
                return (MultiMeshContainer)MultiMesh[Origin].Target;
            }
            else
            {
                //нужно загрузить
                MultiMeshContainer _MConteints = null;

                if (File.Exists(FileName + ".material"))
                {
                    FileName += ".material";
                }
                else if (File.Exists(FileName + ".model"))
                {
                    FileName += ".model";
                }

                string extension = System.IO.Path.GetExtension(FileName);

                if (extension.ToLower() == ".model")
                {
                    try
                    {
                        System.IO.FileStream fs = new System.IO.FileStream(FileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                        Serialize.MeshesContainer MC = Serializer.Deserialize<Serialize.MeshesContainer>(fs);
                        fs.Close();
                        if (MC != null) _MConteints = new MultiMeshContainer(MC);
                        else return null;
                    }
                    catch (System.Exception ex)
                    {
                        System.Windows.Forms.MessageBox.Show("Error! Ошибкак чтения модели: " + FileName);
                        new FileNotFoundException("Файл", FileName);
                    }
                }
                else if (extension.ToLower() == ".material")
                {
#if !DEBUG
                    try
#endif
                    {
                        Serialize.MeshesContainer MC = new Serialize.MeshesContainer();
                        XmlSerializer dsr = new XmlSerializer(MC.GetType());
                        FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
                        MC = (Serialize.MeshesContainer)dsr.Deserialize(fs);
                        fs.Close();
  
                        fs = new System.IO.FileStream(Path.ChangeExtension(FileName, ".geometry"), System.IO.FileMode.Open, System.IO.FileAccess.Read);
                        Serialize.GeometryData GD = Serializer.Deserialize<Serialize.GeometryData>(fs);
                        fs.Close();

                        if (MC != null && GD != null)
                        {
                            MC.Geometry = GD;
                            _MConteints = new MultiMeshContainer(MC);
                        }
                        else return null;
                    }
                    //TODO Debug
#if !DEBUG
                    catch (System.Exception ex)
                    {
                        System.Windows.Forms.MessageBox.Show("Error! Ошибкак чтения модели: " + FileName);
                        new FileNotFoundException("Файл", FileName);
                    }
#endif       
                }
                else
                {
                    System.Windows.Forms.MessageBox.Show("Error! Неверный тип файла: " + FileName);
                    new FileNotFoundException("Неверный тип файла", FileName);
                }

                WeakReference wr = new WeakReference(_MConteints, false);
                MultiMesh.Add(Origin, wr);
                _MConteints.Name = Origin;
                return _MConteints;
                //TODO проверка на валидность загрузки
            }
        }
        
        /// <summary>
        /// Получить эффект
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="inputElement"></param>
        /// <returns></returns>
        public static EffectContainer LoadEffect(string FileName, InputElement[] inputElement)
        {
            string Origin = FileName;
            if (Effects.ContainsKey(Origin) && Effects[Origin].IsAlive)
            {
                //Уже есть
                EffectContainer t = (EffectContainer)Effects[Origin].Target;
                return t;
            }
            else
            {
                //нужно загрузить
                if (File.Exists(FileName + ".fx"))
                {
                    FileName += ".fx";
                }
                else if (File.Exists(FileName + ".ees"))
                {
                    FileName += ".ees";
                }
                EffectContainer t = new EffectContainer(ModelViewer.Program.device, FileName, inputElement);
                t.Name = Origin;
                WeakReference wr = new WeakReference(t, false);
                Effects.Add(Origin, wr);
                return t;
                //TODO проверка на валидность загрузки
            }
        }

        /// <summary>
        /// Получить эффект c дефолтным InputElement[]
        /// </summary>
        /// <param name="FileName"></param>
        /// <param name="inputElement"></param>
        /// <returns></returns>
        public static EffectContainer LoadEffect(string FileName)
        {
            string Origin = FileName;
            if (Effects.ContainsKey(Origin) && Effects[Origin].IsAlive)
            {
                //Уже есть
                EffectContainer t = (EffectContainer)Effects[Origin].Target;
                return t;
            }
            else
            {
                //нужно загрузить
                if (File.Exists(FileName + ".fx"))
                {
                    FileName += ".fx";
                }
                else if (File.Exists(FileName + ".ees"))
                {
                    FileName += ".ees";
                }

                InputElement[] inputElement = new InputElement[]{ 
                new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0),
                new InputElement("TEXCOORD", 0, Format.R16G16_Float, 12, 0),
                new InputElement("TEXCOORD", 1, Format.R16G16_Float, 16, 0),
                new InputElement("NORMAL", 0, Format.R8G8B8A8_UNorm, 20, 0),//w = bone3.w[0]
                new InputElement("TANGENT", 0, Format.R8G8B8A8_UNorm, 24, 0),//w = bone3.w[1]
                new InputElement("BLENDINDICES", 0, Format.R8G8B8A8_UNorm, 28, 0)};// bone3.w[3], bone3.i[0], bone3.i[1], bone3.i[2]

                EffectContainer t = new EffectContainer(ModelViewer.Program.device, FileName, inputElement);
                t.Name = Origin;
                WeakReference wr = new WeakReference(t, false);
                Effects.Add(Origin, wr);
                return t;
                //TODO проверка на валидность загрузки
            }
        }

        public static TextureContainer LoadTexture2D(string FileName)
        {
            string Origin = FileName;
            if (Textures.ContainsKey(Origin) && Textures[Origin].IsAlive)
            {
                //Уже есть
                TextureContainer t = (TextureContainer)Textures[Origin].Target;
                return t;
            }
            else
            {
                //нужно загрузить
                TextureContainer t = new TextureContainer();
                t.Name = FileName;
                t.Texture2d = (Texture2D)Texture2D.FromFile(ModelViewer.Program.device, FileName + ".dds");
                t.TextureResource = new ShaderResourceView(ModelViewer.Program.device, t.Texture2d);
                WeakReference wr = new WeakReference(t, false);

                Textures.Add(Origin, wr);
                return t;
                //TODO проверка на валидность загрузки
            }
        }

        public static void FreeResources()
        {
            foreach (var country in Meshes)
            {
                if (country.Value != null && ((WeakReference)country.Value).IsAlive)
                    ((MeshContainer)((WeakReference)country.Value).Target).Dispose();
            }
            foreach (var country in Effects)
            {
                if (country.Value != null && ((WeakReference)country.Value).IsAlive)
                    ((EffectContainer)((WeakReference)country.Value).Target).Dispose();
            }
            foreach (var country in Textures)
            {
                if (country.Value != null && ((WeakReference)country.Value).IsAlive)
                    ((TextureContainer)((WeakReference)country.Value).Target).Dispose();
            }

            Meshes.Clear();
            Effects.Clear();
            Textures.Clear();
        }
    }
}
